package domain.parsers;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import domain.grammar.ExtendedNonTerminalSymbol;
import domain.grammar.ExtendedTerminalSymbol;
import domain.grammar.LambdaSymbol;
import domain.regularExpression.NonUnaryRegularExpressionOperator;
import domain.regularExpression.RegularExpression;
import domain.regularExpression.RegularExpressionElement;
import domain.regularExpression.UnaryRegularExpressionOperator;

public class RegularExpressionParser {
	private String _notation; 

	/*
	 * production5:		PD1 --> PD2 PD11
	 * production6:		PD11 --> lamda
	 * production7:		PD11 --> | PD1
	 * production8:		PD2 --> PD3 PD2
	 * production9:		PD2 --> lamda
	 * production10:	PD3 --> n PD31
	 * production11:	PD3 --> t PD31
	 * production12:	PD3 --> ( PD1 ) PD31
	 * production13:	PD31 --> lamda
	 * production14:	PD31 --> PD32
	 * production15:	PD32 --> + PD31
	 * production16:	PD32 --> ? PD31
	 * production17:	PD32 --> * PD31
	 * 
	 */
	
	public RegularExpressionParser(){
		_notation = "Notation:\n" +
			"'n' represents a non terminal Symbol and must be a capital character\n"+
			"'t' represents a non terminal Symbol and must be a non-capital character\n";
	}
	
	public RegularExpression parse(FileInputStream fis) throws IOException, ParseException{
		InputStreamReader isr = new InputStreamReader(fis);
		return parse(isr);
	}
	
	public RegularExpression parse(InputStreamReader isr) throws IOException, ParseException{
		LL1Reader ll1Reader = new LL1Reader(isr);
		return new RegularExpression(parse_PD1_production(ll1Reader));
	}
		
	
	

	
	public RegularExpression parse(LL1Reader ll1Reader) throws IOException, ParseException{
		return new RegularExpression(parse_PD1_production(ll1Reader));
	}
	
	
	private RegularExpressionElement parse_PD1_production(LL1Reader ll1Reader) throws ParseException, IOException {
		Set<Character> production5_DirectrixSymbs = new HashSet<Character>();
		production5_DirectrixSymbs.add('|');
		production5_DirectrixSymbs.add('n');
		production5_DirectrixSymbs.add('t');
		production5_DirectrixSymbs.add('(');
		production5_DirectrixSymbs.add(';');
		production5_DirectrixSymbs.add(')');
		
		if(production5_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			//* production5:		PD1 --> PD2 PD11
			RegularExpressionElement ree1 = parse_PD2_production(ll1Reader);
			return parse_PD11_production(ll1Reader,ree1);
		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production5_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}
	
	



	private RegularExpressionElement parse_PD2_production(LL1Reader ll1Reader) throws ParseException, IOException {
		// TODO Auto-generated method stub
		Set<Character> production8_DirectrixSymbs = new HashSet<Character>();
		production8_DirectrixSymbs.add('n');
		production8_DirectrixSymbs.add('t');
		production8_DirectrixSymbs.add('(');
		
		Set<Character> production9_DirectrixSymbs = new HashSet<Character>();
		production9_DirectrixSymbs.add(';');
		production9_DirectrixSymbs.add('|');
		production9_DirectrixSymbs.add(')');

		if(production8_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			//production8:		PD2 --> PD3 PD2
			RegularExpressionElement ree1 = parse_PD3_production(ll1Reader);
			RegularExpressionElement ree2 = parse_PD2_production(ll1Reader);
			
			List<RegularExpressionElement> subElems = new Vector<RegularExpressionElement>();
			subElems.add(ree1);
			if(ree2 instanceof LambdaSymbol){
				return ree1;
			}else if(ree2 instanceof NonUnaryRegularExpressionOperator){
				NonUnaryRegularExpressionOperator noUnOp = (NonUnaryRegularExpressionOperator) ree2;
				if(noUnOp.isConcatenation()){
					subElems.addAll(noUnOp.getSubElements());
				}else{
					subElems.add(ree2);
				}
			}else{
				subElems.add(ree2);
			}
			return NonUnaryRegularExpressionOperator.concatenation(subElems);
		}else if(production9_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			//production9:		PD2 --> lamda
			return new LambdaSymbol();
		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production8_DirectrixSymbs);
			expected.addAll(production9_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}
	
	private RegularExpressionElement parse_PD3_production(LL1Reader ll1Reader) throws ParseException, IOException {
		// TODO Auto-generated method stub
		Set<Character> production10_DirectrixSymbs = new HashSet<Character>();
		production10_DirectrixSymbs.add('n');
		
		Set<Character> production11_DirectrixSymbs = new HashSet<Character>();
		production11_DirectrixSymbs.add('t');
		
		Set<Character> production12_DirectrixSymbs = new HashSet<Character>();
		production12_DirectrixSymbs.add('(');
		
		if(production10_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			//* production10:	PD3 --> n PD31
			Character ntName = (char)ll1Reader.getCurrentChar();
			ExtendedNonTerminalSymbol extNTSymb = new ExtendedNonTerminalSymbol(ntName.toString());
			ll1Reader.readNext();
			return parse_PD31_production(ll1Reader,extNTSymb);
		}else if(production11_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			//* production11:	PD3 --> t PD31
			Character tName = (char)ll1Reader.getCurrentChar();
			ExtendedTerminalSymbol extTSymb = new ExtendedTerminalSymbol(tName.toString());
			ll1Reader.readNext();
			return parse_PD31_production(ll1Reader,extTSymb);
		}else if(production12_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			//* production12:	PD3 --> ( PD1 ) PD31
			ll1Reader.readNext();
			RegularExpressionElement ree = parse_PD1_production(ll1Reader);
			if((char)ll1Reader.getCurrentChar()!=')'){
				List<Character> expected = new Vector<Character>();
				expected.add(')');
				throw new ParseException(	ll1Reader.actualLine(), 
															ll1Reader.actualColumn(), 
															(char)ll1Reader.getCurrentChar(), 
															expected,
															_notation);
			}
			ll1Reader.readNext();
			return parse_PD31_production(ll1Reader,ree);

			
		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production11_DirectrixSymbs);
			expected.addAll(production12_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}
	
	
	
	

	private RegularExpressionElement parse_PD31_production(LL1Reader ll1Reader, RegularExpressionElement subExp) throws ParseException, IOException {
		// TODO Auto-generated method stub
		Set<Character> production13_DirectrixSymbs = new HashSet<Character>();
		production13_DirectrixSymbs.add('n');
		production13_DirectrixSymbs.add('t');
		production13_DirectrixSymbs.add('(');
		production13_DirectrixSymbs.add(';');
		production13_DirectrixSymbs.add('|');
		production13_DirectrixSymbs.add(')');
		
		Set<Character> production14_DirectrixSymbs = new HashSet<Character>();
		production14_DirectrixSymbs.add('*');
		production14_DirectrixSymbs.add('?');
		production14_DirectrixSymbs.add('+');
		
		if(production13_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			return subExp;
		}else if(production14_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			return parse_PD32_production(ll1Reader,subExp);
		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production13_DirectrixSymbs);
			expected.addAll(production14_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}
	

	private RegularExpressionElement parse_PD32_production(
			LL1Reader ll1Reader, RegularExpressionElement subExp) throws ParseException, IOException {
		
		Set<Character> production15_DirectrixSymbs = new HashSet<Character>();
		production15_DirectrixSymbs.add('+');
		Set<Character> production16_DirectrixSymbs = new HashSet<Character>();
		production16_DirectrixSymbs.add('?');
		Set<Character> production17_DirectrixSymbs = new HashSet<Character>();
		production17_DirectrixSymbs.add('*');
		
		if(production15_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){

			ll1Reader.readNext();
			RegularExpressionElement ree = UnaryRegularExpressionOperator.plus(subExp);
			return parse_PD31_production(ll1Reader,ree);
		}else if(production16_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){

			ll1Reader.readNext();
			RegularExpressionElement ree = UnaryRegularExpressionOperator.interrogation(subExp);
			return parse_PD31_production(ll1Reader,ree);
		}else if(production17_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){

			ll1Reader.readNext();
			RegularExpressionElement ree = UnaryRegularExpressionOperator.asterisk(subExp);
			return parse_PD31_production(ll1Reader,ree);
		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production15_DirectrixSymbs);
			expected.addAll(production16_DirectrixSymbs);
			expected.addAll(production17_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}

	private RegularExpressionElement parse_PD11_production(LL1Reader ll1Reader, RegularExpressionElement subElement) throws ParseException, IOException {
		// TODO Auto-generated method stub
		Set<Character> production6_DirectrixSymbs = new HashSet<Character>();
		production6_DirectrixSymbs.add(';');
		production6_DirectrixSymbs.add(')');
		Set<Character> production7_DirectrixSymbs = new HashSet<Character>();
		production7_DirectrixSymbs.add('|');
		
		if(production6_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){
			return subElement;
		}else if(production7_DirectrixSymbs.contains(transformTerminalAndNonTerminal((char)ll1Reader.getCurrentChar()))){

			if((char)ll1Reader.getCurrentChar()!='|'){
				List<Character> expected = new Vector<Character>();
				expected.add('|');
				throw new ParseException(	ll1Reader.actualLine(), 
															ll1Reader.actualColumn(), 
															(char)ll1Reader.getCurrentChar(), 
															expected,
															_notation);
			}
			ll1Reader.readNext();
			List<RegularExpressionElement> subElems = new Vector<RegularExpressionElement>();
			subElems.add(subElement);
			RegularExpressionElement rightRee = parse_PD1_production(ll1Reader);
			if(rightRee instanceof NonUnaryRegularExpressionOperator){
				NonUnaryRegularExpressionOperator noUnOp = (NonUnaryRegularExpressionOperator)rightRee;
				if(noUnOp.isPipe()){
					subElems.addAll(noUnOp.getSubElements());
				}else{
					subElems.add(rightRee);
				}
			}else{
				subElems.add(rightRee);
			}
			
			return NonUnaryRegularExpressionOperator.pipe(subElems);
		}else{
			List<Character> expected = new Vector<Character>();
			expected.addAll(production7_DirectrixSymbs);
			throw new ParseException(	ll1Reader.actualLine(), 
														ll1Reader.actualColumn(), 
														(char)ll1Reader.getCurrentChar(), 
														expected,
														_notation);
		}
	}
	
	private Character transformTerminalAndNonTerminal(Character aChar){
		int asciiCode = aChar;
		if(65 <= asciiCode && asciiCode <=90){
			return 'n';
		}else if(97 <= asciiCode && asciiCode <=122){
			return 't';
		}else{
			return aChar;
		}
	}

}
